#pragma once

#include <iostream>
#include <sys/select.h>
#include <string>
#include <functional>
#include "Sock.hpp"

using namespace std;

namespace Select_sv
{
    static const int defaultport = 8080;         // 默认端口号
    static const int fdnum = sizeof(fd_set) * 8; // 可使用的套接字数量
    static const int defaultfd = -1;             // 默认套接字标志
    using func_t = function<string(const string &)>;
    class SelectServer
    {
    public:
        SelectServer(func_t f, int port = defaultport) : _func(f), _port(port), _listensock(-1), _fdarray(nullptr)
        {
        }
        void initServer()
        {
            // 获取套接字
            _listensock = Sock::Socket();
            cout << "Sock success" << endl;
            // 绑定网络信息
            Sock::Bind(_listensock, _port);
            cout << "Bind success" << endl;
            // 把套接字设置为监听状态
            Sock::Listen(_listensock);
            cout << "Listen success" << endl;
            // 给每一个套接字都设置一个数组，保存套接字的设置情况
            cout << "fd_set size: " << sizeof(fd_set) << endl;
            _fdarray = new int[fdnum];
            for (int i = 0; i < fdnum; i++)
                _fdarray[i] = defaultfd; // 将每个套接字状态都设置为默认（未使用状态）

            _fdarray[0] = _listensock; // 第一个设置的套接字是通信套接字，供accept函数使用-建立连接
            cout << "initServer" << endl;
        }
        void Print()
        {
            cout << "now using socket: ";
            for (int i = 0; i < fdnum; i++)
            {
                if (_fdarray[i] != defaultfd)
                    cout << _fdarray[i] << " "; // 将设置进数组内的套接字进行打印
            }
            cout << endl;
        }
        void Accpter(int lsock)
        {
            logMessage(DEBUG, "Accepter begin");
            string clientip;
            uint16_t clientport = 0;
            int sock = Sock::Accpet(lsock, &clientip, &clientport); // 若成功返回，返回一个用于通信的套接字
            if (sock < 0)
                return;
            logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);

            int i = 0;
            for (; i < fdnum; i++)
            {
                if (_fdarray[i] != defaultfd)
                    continue;
                else
                    break;
            }
            if (i == fdnum) // 遍历完全部socket发现没用可使用的套接字
            {
                logMessage(WARNING, "server is full,please wait");
                close(sock); // 关闭用于通信的套接字，重新建立连接
               // _fdarray[i] = defaultfd;不需要去除，规定数组的0号下标对应的位置是专门用来拿连接的
            }
            else
            {
                _fdarray[i] = sock; // 把用于通信的套接字给select监管，让它等待
            }
            Print();
            logMessage(DEBUG, "Accepter end");
        }

        void Recver(int sock, int pos)
        {
            logMessage(DEBUG, "Recver begin");
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if (s > 0)
            {
                buffer[s] = 0;
                cout << "client# " << buffer << endl;
            }
            else if (s == 0)
            {
                close(sock);               // 关闭该套接字，关闭通信通道
                _fdarray[pos] = defaultfd; // 将数组中的该套接字清除
                logMessage(NORMAL, "client quit");
                return;
            }
            else
            {
                close(sock);
                _fdarray[pos] = defaultfd; // 将数组中的该套接字清除
                logMessage(ERROR, "recv error");
                return;
            }
            // 将客户端发来的数据原样写回去
            string resp = _func(buffer);

            write(sock, resp.c_str(), resp.size()); // 写回去
            logMessage(DEBUG, "Recever end");
        }
        void Handlerop(fd_set &rfds)
        {
            for (int i = 0; i < fdnum; i++)
            {
                if (_fdarray[i] == defaultfd)
                    continue;

                if (FD_ISSET(_fdarray[i], &rfds) && (_fdarray[i] == _listensock))
                // 此时i对应的数组位置是拿到连接的文件描述符，意味着在底层连接已经拿到，等待上层提取
                {
                    Accpter(_listensock);
                }
                else if (FD_ISSET(_fdarray[i], &rfds)) // 此时存在数组内的对应套接字都是底层读资源就绪
                {
                    Recver(_fdarray[i], i);
                }
                else
                {
                }
            }
        }
        void Start()
        {
            // 将数组管理的套接字设置进fd_set类型的结构内
            for (;;)
            {
                fd_set rfds;    // 当前程序只关心读事件
                FD_ZERO(&rfds); // 对该结构（位图）清空
                int maxfd = _fdarray[0];
                for (int i = 0; i < fdnum; i++)
                {
                    if (_fdarray[i] == defaultfd)
                        continue;
                    FD_SET(_fdarray[i], &rfds); // 将需要使用的套接字设置进读事件结构中
                    if (maxfd < _fdarray[i])
                        maxfd = _fdarray[i]; // 更新最大文件描述符
                    cout << "listensock set to _fdarray success" << endl;
                }

                // 把读事件交给select监管
                cout << "will select " << endl;
                int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // 阻塞式监管
                cout << "select end" << endl;
                switch (n)
                {
                case 0:
                    logMessage(NORMAL, "timeout..."); // 监管时间内没用套接字就绪，即超时返回
                    break;
                case -1:
                    logMessage(WARNING, "select error,error:%d, error string: ", errno, strerror(errno));
                    break;
                default:
                    logMessage(NORMAL, "get a new link..."); // 拿到新连接，即拿到通信的连接,客户端主动断开连接后，为何后续循环select都是拿到连接？
                    Handlerop(rfds);
                    break;
                }
            }
        }

        ~SelectServer()
        {
            if (_listensock < 0) // 为什么是小于0？
                close(_listensock);
            if (_fdarray)
                delete[] _fdarray;
        }

    private:
        int _port;
        int _listensock;
        int *_fdarray; // 记录需要交给select管理的套接字，每个套接字交给select管理的方式是传递整数给位图，因此该数组的类型也是整数int
        func_t _func;
    };
}